#region (c) 2014 THN Solutions LLC. - All rights reserved
/*
Simplified BSD License (BSD)
Copyright (c) 2014, THN Solutions LLC. ( www.ThnSolutions.com )
Author: Nguyễn, M. Hải                 ( www.Minh-Hai.com     )
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, this 
      list of conditions and the following disclaimer.

    * Redistributions in binary form must reproduce the above copyright notice, 
      this list of conditions and the following disclaimer in the documentation 
      and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/
#endregion

#region Record of Change
//             							CHANGE HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | DEVELOPER  | DESCRIPTION                                              |
// -------------------------------------------------------------------------------------
// | 12-Nov-09 | HaiNM      | First creation.                                          |
// -------------------------------------------------------------------------------------
//
#endregion

#region Record of Audit
//             							REVIEW HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | REVIEWER   | NOTES                                                    |
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
//
#endregion

#region Using directives
using System;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;

using Thn;
using System.Xml;
#endregion

namespace Thn.Interface.Vcl
{
    /// <summary>
    /// Abstract base implementation of <see cref="IPageManager"/>.
    /// </summary>
#if DEBUG
    public
#else
    public
#endif
 abstract class PageManagerBase : Component, IPageManager
    {
        #region Fields

        /// <summary>
        /// Cache of page descriptors by id
        /// <para>key  : pageID</para>
        /// <para>value: page descriptor</para>
        /// </summary>
        Hashtable mPages = new Hashtable();

        #endregion

        #region Properties

        #region Target
        private Control mTarget;
        /// <summary>
        /// Gets/Sets target location to show pages
        /// </summary>
        [Browsable(true), DefaultValue(null), Category("Data"), Description("Target location to show pages")]
        public Control Target
        {
            get { return mTarget; }
            set { mTarget = value; }
        }
        #endregion

        #endregion

        #region Load Map
        #region Load map from Xml
        /// <summary>
        /// Load page map from xml file
        /// </summary>
        public void LoadMap(string filename)
        {
            if (System.IO.File.Exists(filename))
            {
                XmlDocument doc = new XmlDocument();
                doc.Load(filename);

                for (int y = 0; y < doc.ChildNodes.Count; y++)
                {
                    XmlNode root = doc.ChildNodes[y];

                    if (root.Name == "PageMap")
                    {
                        //if I have children, parse them
                        if (root.ChildNodes.Count > 0)
                        {
                            for (int i = 0; i < root.ChildNodes.Count; i++)
                            {
                                XmlNode node = root.ChildNodes[i];

                                if (node is XmlElement) ParsePage((XmlElement)node, null);
                            }
                        }
                        break;
                    }
                }
            }//exists file
            else throw new System.IO.FileNotFoundException();
        }
        #endregion

        #region Parse Page
        void ParsePage(XmlElement element, IPageDescriptor parent)
        {
            IPageDescriptor descriptor = null;

            string name = "";
            string id = "";
            string typeName = "";
            Type pageType = null;

            //parse attributes
            foreach (XmlAttribute attribute in element.Attributes)
            {
                string attributeName = attribute.Name.ToUpper();

                if (attributeName == "NAME") name = attribute.Value;
                else if (attributeName == "ID") id = attribute.Value;
                else if (attributeName == "TYPE")
                {
                    typeName = attribute.Value;

                    //try to resolve the corresponding type
                    pageType = TypeResolver.Resolve(typeName);

                    if (pageType == null) throw new Exception("Could not resolve type [" + typeName + "]");
                }
            }

            //register page
            if ((id != "") && (pageType != null))
            {
                descriptor = CreateDescriptor(id, name, parent, pageType);
                Register(descriptor);

                //if I have children, parse them
                if (element.ChildNodes.Count > 0)
                {
                    for (int i = 0; i < element.ChildNodes.Count; i++)
                    {
                        XmlNode node = element.ChildNodes[i];

                        if (node is XmlElement) ParsePage((XmlElement)node, descriptor);
                    }
                }
            }
            else
            {
                if (id == "") throw new Exception("Please review the page map file. Make sure all element has ID attribute");
                else throw new Exception("Cannot resolve type [" + typeName + "]");
            }
        }
        #endregion
        #endregion

        #region Create PageDescriptor
        /// <summary>
        /// Constructs a new page descriptor
        /// </summary>
        protected virtual IPageDescriptor CreateDescriptor(string id, string name, IPageDescriptor parent, Type pageType)
        {
            PageDescriptor result = new PageDescriptor();
            result.Name = name;
            result.PageType = pageType;

            if (parent != null)
            {
                result.Parent = parent;
                result.ID = parent.ID + "." + id;
            }
            else result.ID = id;

            return result;
        }
        #endregion

        #region Can Navigate Away
        /// <summary>
        /// Checks whether the user is allowed to navigate away from current page.
        /// </summary>
        protected bool CanNavigateAway(PageShowContext context)
        {
            bool result = false;

            if (mCurrentPage != null) result = mCurrentPage.Page.AllowNavigationAway(context);
            else result = true;

            return result;
        }
        #endregion

        #region Can Navigate To
        /// <summary>
        /// Checks whether the current user is allowed to view the target page.
        /// </summary>
        protected bool CanNavigateTo(PageShowContext context, ref string redirection)
        {
            return context.Descriptor.Page.AllowView(ref redirection);
        }
        #endregion

        #region Show Page
        /// <summary>
        /// Show a page at target location
        /// </summary>
        /// <returns>Whether showing was a success.</returns>
        protected virtual bool ShowPage(PageShowContext context, bool updateHistory)
        {
            bool result = false;

            IPageDescriptor page = context.Descriptor;
            object[] parameters = context.Parameters;

            if (mTarget == null) throw new Exception("Please specify a target location");
            else if (page != mCurrentPage)
            {
                #region Update history
                if (updateHistory)
                {
                    #region History update required

                    if (mCurrentPage != null)
                    {
                        mBackHistory.Push(mCurrentPage);
                    }
                    #region Defensive tracing
                    else
                    {
                        //I have no previous page to add to back history
                    }
                    #endregion

                    mForwardHistory.Clear();
                    mCurrentPage = page;

                    #endregion
                }
                else
                {
                    #region History update not required

                    Log.Information("show {0} - no update", page.Name);
                    //update is not required

                    mCurrentPage = page;

                    #endregion
                }

                #endregion

                #region Hide current page
                if (mCurrentPage != null) HidePage(mCurrentPage);
                #endregion

                #region Show page
                mTarget.SuspendLayout();
                mTarget.Controls.Clear();
                System.Windows.Forms.Control con = (System.Windows.Forms.Control)page.Page;
                if (con is PageBase) ((PageBase)con).Translate();
                con.Dock = System.Windows.Forms.DockStyle.Fill;
                mTarget.Controls.Add(con);
                mTarget.ResumeLayout();
                ((IPage)con).Show(this, context);
                con.Focus();
                #endregion

                #region Raise events

                RaisePageShown(context);

                #endregion
            }
            #region Defensive tracing
            else
            {
                //no need to show current page again
            }
            #endregion

            return result;
        }
        #endregion

        #region Show Error Page
        IPageDescriptor mErrorPage = null;

        /// <summary>
        /// Show an error page
        /// </summary>
        protected virtual void ShowErrorPage(string errorMessage)
        {
            //lazy initialize the error page
            if (mErrorPage == null)
            {
                mErrorPage = CreateDescriptor("Vcl.ErrorPage", "Error", null, typeof(ErrorPage));
            }

            ErrorPage page = (ErrorPage)(mErrorPage.Page);
            page.UpdateErrorMessage(errorMessage);

            PageShowContext context = new PageShowContext();
            context.Descriptor = mErrorPage;
            context.PermissionRequired = false;
            ShowPage(context, true);
        }
        #endregion

        #region Hide Page
        /// <summary>
        /// Turn off visibility of a page
        /// </summary>
        protected virtual void HidePage(IPageDescriptor page)
        {
            page.Page.NotifyHide();
            Control con = (Control)page.Page;
            con.Parent = null;
            RaisePageHidden(page);
        }
        #endregion

        #region IPageManger Members

        #region Register
        /// <summary>
        /// Register a page to be managed by this manager.
        /// <para>Note that the page will be root-page (no parent)</para>
        /// </summary>
        /// <param name="pageID">Identifier of page</param>
        /// <param name="pageType">Type of page</param>
        public void Register(string pageID, string name, Type pageType)
        {
            IPageDescriptor descriptor = CreateDescriptor(pageID, name, null, pageType);
            Register(descriptor);
        }

        /// <summary>
        /// Register a page to be managed by this manager.
        /// <para>The page's hierachy as well as control type will be obtained from descriptor</para>
        /// </summary>
        /// <param name="descriptor">Description of page</param>
        public void Register(IPageDescriptor descriptor)
        {
            if (mPages.Contains(descriptor.ID))
            {
                Thn.Log.Warning(string.Format("Page descriptor with ID [{0}] has been registered. Overwritting old one", descriptor.ID));
                mPages.Remove(descriptor.ID);
            }

            mPages.Add(descriptor.ID, descriptor);
        }
        #endregion

        #region Show
        /// <summary>
        /// Display a page
        /// </summary>
        /// <param name="pageID">Identifier of page</param>
        public void Show(string pageID)
        {
            if (mPages.Contains(pageID))
            {
                IPageDescriptor page = (IPageDescriptor)mPages[pageID];
                if (page != mCurrentPage)
                {
                    PageShowContext context = new PageShowContext();
                    context.Descriptor = page;
                    Show(context);
                }
            }
            else ShowErrorPage("Cannot find page [" + pageID + "]");
        }

        /// <summary>
        /// Display a page with specified parameters
        /// </summary>
        /// <param name="pageID">Identifier of page</param>
        /// <param name="parameters">Optional parameters for the page</param>
        public void Show(string pageID, params object[] parameters)
        {
            if (mPages.Contains(pageID))
            {
                IPageDescriptor page = (IPageDescriptor)mPages[pageID];
                if (page != mCurrentPage)
                {
                    PageShowContext context = new PageShowContext();
                    context.Descriptor = page;
                    context.Parameters = parameters;
                    Show(context);
                }
            }
            else ShowErrorPage("Cannot find page [" + pageID + "]");
        }

        /// <summary>
        /// Display a page
        /// </summary>
        /// <param name="descriptor">Page description</param>
        public void Show(IPageDescriptor descriptor)
        {
            if (descriptor != mCurrentPage)
            {
                PageShowContext context = new PageShowContext();
                context.Descriptor = descriptor;
                Show(context);
            }
        }

        /// <summary>
        /// Display a page with specified parameters
        /// </summary>
        /// <param name="descriptor">Page description</param>
        /// <param name="parameters">Optional parameters for the page</param>
        public void Show(IPageDescriptor descriptor, params object[] parameters)
        {
            if (descriptor != mCurrentPage)
            {
                PageShowContext context = new PageShowContext();
                context.Descriptor = descriptor;
                context.Parameters = parameters;
                Show(context);
            }
        }

        /// <summary>
        /// Display a page
        /// </summary>
        /// <param name="context">The request context.</param>
        public void Show(PageShowContext context)
        {
            if (context.Descriptor != mCurrentPage)
            {
                if (CanNavigateAway(context))
                {
                    string redirection = "";

                    if (CanNavigateTo(context, ref redirection))
                    {
                        //user is allowed to view the page
                        ShowPage(context, true);
                    }
                    else
                    {
                        //user is not allowed to view -> Redirect user
                        if ((redirection != null) && (redirection != ""))
                        {
                            Show(redirection);
                        }
                        #region Defensive tracing
                        else
                        {
                            //redirection not available
                        }
                        #endregion
                    }
                }
                #region Defensive tracing
                else
                {
                    //not allowed to navigate away
                }
                #endregion
            }
        }
        #endregion

        #region Current Page
        public IPageDescriptor mCurrentPage;

        /// <summary>
        /// Gets the page being shown
        /// </summary>
        public IPageDescriptor CurrentPage
        { get { return mCurrentPage; } }
        #endregion

        #region Get Descriptor
        /// <summary>
        /// Find the page descriptor
        /// </summary>
        /// <param name="pageID">Identifier of page</param>
        public IPageDescriptor GetDescriptor(string pageID)
        {
            IPageDescriptor result = null;

            if (mPages.Contains(pageID)) result = (IPageDescriptor)mPages[pageID];

            return result;
        }
        #endregion

        #region Navigation
        ArrayList lstHistory = new ArrayList();

        #region Clear History
        /// <summary>
        /// Clear navigation history
        /// </summary>
        public void ClearHistory()
        {
            lstHistory.Clear();
            mCurrentPageIndex = 0;
        }
        #endregion

        #region CurrentPageIndex
        private int mCurrentPageIndex = 0;
        /// <summary>
        /// Gets/Sets index of current page
        /// </summary>
        [Browsable(false)]
        protected int CurrentPageIndex
        {
            get { return mCurrentPageIndex; }
            set { mCurrentPageIndex = value; }
        }
        #endregion

        #region Max History
        private int mMaxHistory = 10;
        /// <summary>
        /// Gets/Sets the number of pages remembered for navigating
        /// </summary>
        [Browsable(true), DefaultValue(6), Description("Number of pages for navigation")]
        public int MaxHistory
        {
            get { return mMaxHistory; }
            set
            {
                mMaxHistory = value;
                mBackHistory.Capacity = value;
                mForwardHistory.Capacity = value;
            }
        }
        #endregion

        #region Forward
        /// <summary>
        /// Move to next page
        /// </summary>
        public void Forward()
        {
            if (CanForward)
            {
                IPageDescriptor page = mForwardHistory.Current;
                if (page != null)
                {
                    PageShowContext context = new PageShowContext();
                    context.Descriptor = page;

                    if (CanNavigateAway(context))
                    {
                        string redirection = "";
                        if (CanNavigateTo(context, ref redirection))
                        {
                            #region User is allowed to view the page

                            //pop from forward history
                            mForwardHistory.Pop();

                            //push to back history
                            if (mCurrentPage != null)
                            {
                                mBackHistory.Push(mCurrentPage);
                            }
                            #region Defensive tracing
                            else
                            {
                                //I have no current page to keep track of
                            }
                            #endregion

                            //Show page
                            ShowPage(context, false);

                            #endregion
                        }
                        else
                        {
                            #region User is not allowed to view this restricted page

                            if ((redirection != null) && (redirection != ""))
                            {
                                //redirect user
                                Show(redirection);
                            }
                            #region Defensive tracing
                            else
                            {
                                //redirection not available, simply do nothing
                            }
                            #endregion

                            #endregion
                        }
                    }
                    #region Defensive tracing
                    else
                    {
                        //not allowed to navigate away
                    }
                    #endregion
                }
                #region Defensive tracing
                else
                {
                    Log.Warning("Cannot navigate forward because page is null");
                }
                #endregion
            }
        }
        #endregion

        #region Can Forward
        /// <summary>
        /// Is there any page to move forward
        /// </summary>
        [Browsable(false)]
        public bool CanForward
        {
            get
            {
                return mForwardHistory.Count > 0;
            }
        }
        #endregion

        #region Back
        /// <summary>
        /// Move to previous page
        /// </summary>
        [Browsable(false)]
        public void Back()
        {
            if (CanBack)
            {
                IPageDescriptor page = mBackHistory.Current;
                if (page != null)
                {
                    PageShowContext context = new PageShowContext();
                    context.Descriptor = page;

                    if (CanNavigateAway(context))
                    {
                        string redirection = "";

                        if (CanNavigateTo(context, ref redirection))
                        {
                            #region User is allowed to view the page

                            //pop from back history
                            mBackHistory.Pop();

                            //push to forward history
                            if (mCurrentPage != null)
                            {
                                mForwardHistory.Push(mCurrentPage);
                            }
                            #region Defensive tracing
                            else
                            {
                                //I have no current page to keep track of
                            }
                            #endregion

                            //Show page                    
                            ShowPage(context, false);

                            #endregion
                        }
                        else
                        {
                            #region User is not allowed to view this restricted page

                            if ((redirection != null) && (redirection != ""))
                            {
                                //redirect user
                                Show(redirection);
                            }
                            #region Defensive tracing
                            else
                            {
                                //redirection not available, simply do nothing
                            }
                            #endregion

                            #endregion
                        }
                    }
                    #region Defensive tracing
                    else
                    {
                        //not allowed to navigate away
                    }
                    #endregion
                }
                #region Defensive tracing
                else
                {
                    Log.Warning("Cannot navigate back because page is null");
                }
                #endregion
            }
        }
        #endregion

        #region Can Back
        /// <summary>
        /// Is there any page to move back
        /// </summary>
        public bool CanBack
        {
            get { return mBackHistory.Count > 0; }
        }
        #endregion

        #region Back History
        private PageHistory mBackHistory = new PageHistory();
        /// <summary>
        /// Gets the page history for forward navigation.
        /// </summary>
        public IPageHistory BackHistory
        {
            get { return mBackHistory; }
        }
        #endregion

        #region Forward History
        private PageHistory mForwardHistory = new PageHistory();
        /// <summary>
        /// Gets the page history for forward navigation.
        /// </summary>
        public IPageHistory ForwardHistory
        {
            get { return mForwardHistory; }
        }
        #endregion

        #endregion

        #region Events

        #region Page Shown
        /// <summary>
        /// This event is raised after a page is shown.
        /// </summary>
        public event PageShownHandler PageShown;

        /// <summary>
        /// Invoke event PageShown
        /// </summary>
        protected void RaisePageShown(PageShowContext context)
        {
            PageShownHandler handler = PageShown;
            if (handler != null) handler(this, context);
        }
        #endregion

        #region Page Hidden
        /// <summary>
        /// This event is raised after a page is hidden.
        /// </summary>
        public event PageEventHandler PageHidden;

        /// <summary>
        /// Invoke event PageHidden
        /// </summary>
        protected void RaisePageHidden(IPageDescriptor page)
        {
            PageEventHandler handler = PageHidden;
            if (handler != null) handler(this, page);
        }
        #endregion

        #endregion

        #endregion

        #region Constructors
        /// <summary>
        /// Default constructor.
        /// </summary>
        public PageManagerBase()
        { }
        #endregion
    }
}
